﻿/*
多态
	多种形态，多态分为编译时多态（静态多态）和运行时多态（动态多态），重点运行时多态（动态多态），
	编译时多态（静态多态）和运行时多态（动态多态）。编译时多态（静态多态）是函数重载和函数模版，
	传不同类型的参数就可以调用不同的函数，通过函数不同达到多种形态，叫编译时多态是因为实参传给形参的参数匹配是在编译时完成
	编译时归为静态，运行时归为动态。
	动态多态的实现本质就是调用虚函数

	运行时多态，具体是去完成某个行为（函数），可以传不同的对象就会完成不同的行为（行为一样），就达到多种形态。
	譬如买票行为：普通人买票全价票，学生买票优惠票，军人买票优先买票。
	同样是动物的一个行为（函数），传猫对象过去就是 喵       传狗对象过去就是  汪

多态定义和实现
	多态构成条件
		多态是一个继承关系下的类对象，去调用同一函数，产生了不同的行为。比如Student继承了Person。Person对象买票全价
		Student对象优惠买票。（注意：虚函数    virtual     解决多继承的数据冗余和二义性）
	实现多态还有两个必须重要条件
		1.必须是基类的指针或者引用 调用虚函数
		2.被调用的函数必须是虚函数，并且完成了虚函数重写/覆盖。（实现多态的两个重要条件   虚函数重写覆盖的三同）

要实现多态效果
	* 第一，必须是基类的指针或引用，因为只有基类的指针或引用才能指向派生类对象；
	* 第二，派生类必须对基类的虚函数重写/覆盖，重写或者覆盖了，派生类才能有不同的函数，多态的不同形态效果才能达到

* 虚函数的重写/覆盖
	 派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
	（三同）返回值类型、函数名字、参数列表完全相同)，称派生类的虚函数重写了基类的虚函数。
	
	注意：在重写基类虚函数时，派生类的虚函数在不加virtual关键字时，虽然也可以构成重写(因为继承
	后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)，但是该种写法不是很规范，不建议这样
	使用

虚函数
	类成员函数前面加上virtual修饰，那么这个成员函数被称为虚函数。注意：非成员函数不能加virtual修饰

重写   是基类的声明和派生类的函数体   共同组成重写    本质是重写虚函数的实现

虚函数重写的时候可能会协变
	派生类重写基类虚函数时，与基类虚函数返回值类型不同，即基类虚函数返回基类对象的指针或者引用，
	派生类虚函数返回派生类对象的指针或者引用时，称为协变。协变的实际意义不大


析构函数的重写
	基类的析构函数为虚函数，此时派生类析构函数只要定义，无论是否加virtual关键字，都与基类的析构函数构成重写，
	虽然基类与派生类析构函数名字不同看起来不符合重写规则，实际上编译器对析构函数的名称做了特殊处理，
	编译后析构函数的名称统一处理成destructor，所以基类的析构函数加了virtual修饰，派生类的析构函数就构成重写

C++ 11
override 和 final关键字
override
	C++对虚函数重写的要求较为严格，但是有些情况由于疏忽，比如函数名写错 参数写错等导致无法构成重写，而这种错误在编译期间不会报错
	只有在程序运行时没有得到预期结果而debug，得不偿失，因此C++11提供了override ，可以帮助用户检测是否重写。
	（在构成重写的后面   编译的时候帮着检查）
final
	如果我们不想让派生类重写这个虚函数，可以用final修饰    
	最终类    修饰的虚函数不能被重写 


函数名相同的函数之间
重载/重写/隐藏的对比（都是函数之间的关系）
	重载
		1.两个函数在同一作用域
		2.函数名相同，参数不同，参数的类型或者个数不同，返回值可同  可不同
	重写/覆盖
		1.两个函数分别在继承体系的父类和子类不同作用域
		2.函数名，参数，返回值都必须相同，协变例外
		3.两个函数都必须是虚函数
	隐藏
		1.两个函数分别在继承体系的父类和子类不同作用域
		2.函数名相同
		3.两个函数只要不构成重写，就是隐藏
		4.父子类的成员变量相同也叫隐藏
	

纯虚函数和抽象类
	在虚函数的后面写上 =0，则这个函数为纯虚函数，纯虚函数不需要定义实现(实现没意义  因为要被派生类重写   但是语法上能实现)
	只要声明就可以。包含纯虚函数的类叫做抽象类，抽象类不能实例化出对象，如果派生类继承后不重写纯虚函数，那么派生类也是抽象类。
	纯虚函数某种程度上强制了派生类重写虚函数，因为不重写实例化不出对象

动态绑定和静态绑定
	1.对不满足多态条件(指针或者引用 + 调用虚函数)的函数调用是在编译时绑定，也就是编译时确定调用函数的地址，叫做静态绑定
	2.满足多态条件的函数调用是在运行时绑定，也就是在运行时到指向对象的虚函数表中找到调用函数的地址，也叫做动态绑定
对象有自己的虚表
	


虚函数表
* 同类型对象的虚表是一样的（共用）   不同类型对象虚表各自独立
* 基类对象的虚函数表中存放基类所有虚函数的地址。
* 同类型的对象共用同一张虚表，不同类型的对象各自有独立的虚表，所以基类和派生类有各自独立的虚表。

1.派生类由两部分构成，继承下来的基类和自己的成员，一般情况下，继承下来的基类中有虚函数表指针，自己就不会再生成虚函数表指针。
但是要注意的这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同一个，就像基类对象的成员和派生类对象中的基类对
象成员也独立的。

2.派生类中重写的基类的虚函数，派生类的虚函数表中对应的虚函数就会被覆盖成派生类重写的虚函数地址。

3.派生类的虚函数表中包含，(1)基类的虚函数地址，(2)派生类重写的虚函数地址完成覆盖，(3)派生类自己的虚函数地址三个部分。

4.虚函数表本质是一个存虚函数指针的指针数组，一般情况这个数组最后门面放了⼀个0x00000000标记。
(这个C++并没有进行规定，各个编译器自行定义的，vs系列编译器会再后面放个0x00000000标记，g++系列编译不会放)

* 虚函数存在哪的？
  虚函数和普通函数一样的，编译好后是一段指令，都是存在代码段的，只是虚函数的地址存到了虚表中。

* 虚函数表存在哪的？
  这个问题严格说并没有标准答案C++标准并没有规定，vs下是存在代码段(常量区)

*/